﻿using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Security.Principal;
using System.Threading;
using System.Web;
using System.Windows.Forms;
using Bespoke.Common.Net;
using Bespoke.Common.Osc;
using HTPCRemoteServer.Properties;

namespace HTPCRemoteServer
{
    delegate void SetTextCallback(Label label, string text);

    public partial class MainForm : Form
    {
        private const int UdpPort = 48727; // HTPCR in a mobile phone dialer
        private readonly OscServer oscServer;
        private readonly float MouseSensitivity = 1.6f;
        private readonly Label[] logLabels;
        private int logPosition;

        public MainForm()
        {
            InitializeComponent();
            this.Text = Resources.WindowTitle;

            WindowsPrincipal pricipal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
            bool hasAdministrativeRight = pricipal.IsInRole(WindowsBuiltInRole.Administrator);
            this.AddressLabel.AppendText(hasAdministrativeRight ? Resources.UacGood : Resources.UacWarning);
            this.AddressLabel.AppendText(Environment.NewLine);

            if (!hasAdministrativeRight)
            {
                this.AddressLabel.Select(0, Resources.UacWarning.Length);
                this.AddressLabel.SelectionColor = Color.Red;
            }

            IPAddress[] addresses = Dns.GetHostAddresses(Dns.GetHostName());
            string serverAddress = String.Empty;

            if (addresses.Length > 0)
            {
                // Find the first IPV4 address as the server address, if the HTPC has more than one IPV4 addresses only one of them is supported
                foreach (IPAddress address in addresses)
                {
                    // IPV4 only and ignore loopback address
                    if ((address.AddressFamily != AddressFamily.InterNetwork) || IPAddress.IsLoopback(address))
                    {
                        continue;
                    }

                    if (serverAddress.Length != 0)
                    {
                        serverAddress += Resources.IPOr;
                    }

                    serverAddress += address.ToString();
                }

                this.AddressLabel.AppendText(String.IsNullOrEmpty(serverAddress) ?
                    Resources.LocalIPaddressNotFound :
                    String.Format(Resources.LocalIPAddressFormat, serverAddress));
            }

            this.RecentLogGroup.Text = Resources.RecentInputTitle;
            this.logLabels = new Label[] { this.Log1, this.Log2, this.Log3, this.Log4, this.Log5 };

            this.oscServer = new OscServer(TransportType.Udp, IPAddress.Any, UdpPort, IPAddress.None, TransmissionType.Unicast);
            this.oscServer.Start();
            this.oscServer.PacketReceived += this.OnPacketReceived;
        }

        private void SetText(Label label, string text)
        {
            label.Text = text;
        }

        private string FormatPacket(OscPacket p)
        {
            switch (p.Data.Length)
            {
                case 0:
                    return p.Address;
                case 1:
                    return String.Format("{0} {1}", p.Address, p.Data[0]);
                case 2:
                    return String.Format("{0} {1} {2}", p.Address, p.Data[0], p.Data[1]);
                case 3:
                    return String.Format("{0} {1} {2} {3}", p.Address, p.Data[0], p.Data[1], p.Data[2]);
            }

            return String.Empty;
        }

        private void LogPacket(OscPacket p)
        {
            Label label = this.logLabels[this.logPosition];
            SetTextCallback d = this.SetText;
            label.Invoke(d, label, this.FormatPacket(p));
            this.logPosition++;

            if (this.logPosition >= this.logLabels.Length)
            {
                this.logPosition = 0;
            }

        }

        private void OnPacketReceived(object sender, OscPacketReceivedEventArgs e)
        {
            this.LogPacket(e.Packet);

            switch (e.Packet.Address)
            {
                case "/text":
                    this.HandleText(e.Packet);
                    break;
                case "/magnifier":
                    this.StartMagnifier();
                    break;
                default:
                    this.HandleInput(e.Packet);
                    break;
            }
        }

        private void StartMagnifier()
        {
            string windDir = Environment.GetEnvironmentVariable("windir");
            Process.Start(Path.Combine(windDir, @"system32\magnify.exe"));
        }

        private void HandleText(OscPacket packet)
        {
            string text = HttpUtility.UrlDecode(packet.Data[0] as string);

            if ((text == null) || (text.Length == 0))
            {
                return;
            }

            foreach (char c in text)
            {
                NativeMethods.INPUT keyInput = new NativeMethods.INPUT();
                keyInput.type = (int)NativeMethods.InputType.INPUT_KEYBOARD;
                keyInput.ki.dwFlags = (int)NativeMethods.KEYEVENTF.UNICODE;
                keyInput.ki.wVk = 0;
                keyInput.ki.wScan = (short)c;
                keyInput.ki.time = 0;
                keyInput.ki.dwExtraInfo = NativeMethods.GetMessageExtraInfo();
                NativeMethods.SendInputEx(keyInput);
                Thread.Sleep(10);
            }
        }

        private void HandleInput(OscPacket packet)
        {
            NativeMethods.INPUT input = new NativeMethods.INPUT();

            switch (packet.Address)
            {
                case "/mouse":
                    input.type = (int)NativeMethods.InputType.INPUT_MOUSE;
                    input.mi.dwFlags = (int)NativeMethods.MOUSEEVENTF.MOVE;
                    input.mi.dx = (int)((float)(packet.Data[0]) * MouseSensitivity);
                    input.mi.dy = (int)((float)(packet.Data[1]) * MouseSensitivity);
                    break;
                case "/keyboard":
                    input.type = (int)NativeMethods.InputType.INPUT_KEYBOARD;
                    input.ki.dwFlags = (int)NativeMethods.KEYEVENTF.KEYDOWN;
                    input.ki.wVk = (short)(int)(packet.Data[0]);
                    input.ki.wScan = (short)NativeMethods.MapVirtualKey((uint)input.ki.wVk, 0);
                    input.ki.time = 0;
                    input.ki.dwExtraInfo = NativeMethods.GetMessageExtraInfo();
                    break;
                case "/leftbutton":
                    input.type = (int)NativeMethods.InputType.INPUT_MOUSE;
                    input.mi.dwFlags = ((int)(packet.Data[0]) == 0) ? (int)NativeMethods.MOUSEEVENTF.LEFTDOWN : (int)NativeMethods.MOUSEEVENTF.LEFTUP;
                    break;
                case "/rightbutton":
                    input.type = (int)NativeMethods.InputType.INPUT_MOUSE;
                    input.mi.dwFlags = ((int)(packet.Data[0]) == 0) ? (int)NativeMethods.MOUSEEVENTF.RIGHTDOWN : (int)NativeMethods.MOUSEEVENTF.RIGHTUP;
                    break;
                case "/wheel":
                    input.type = (int)NativeMethods.InputType.INPUT_MOUSE;
                    input.mi.dwFlags = (int)NativeMethods.MOUSEEVENTF.WHEEL;
                    input.mi.mouseData = (int)(packet.Data[0]) * 120;
                    break;
                default:
                    return;
            }

            NativeMethods.SendInputEx(input);
        }
    }
}
